using UnityEngine;
using System.Collections;
 
/// MainCharacterController -by Ying Yuan



[RequireComponent (typeof (CharacterController))]
public class MainCharacterController: MonoBehaviour {
		CharacterController controller; 
        public float moveSpeed = 0.0f;
        public float jumpHeight = 0.5f; 
        public float gravity = 20.0f;
   
        public Vector3 moveDirection{get; set;}
        public float verticalSpeed{get; set;}
        public Vector3 verticalDirection{get; set;}
		public bool isClockwise{get;set;}
		public float turningTime; //should be set in the inspector
		
		private float _adjustMoveSpeed = 0.0f;
        private float _jumpRepeatTime = 0.05f;
        private bool _toJump = false;
        // Last time the jump button was clicked down
        private float lastJumpButtonTime = -10.0f;
        // Last time we performed a jump
        private float lastJumpTime = -1.0f;
        //private CubeSpawner _spawner;
        private float _hPosBeforeJump, _vPosBeforeJump;
        private float _hPosAfterLanded, _vPosAfterLanded;
        private bool _isGrounded = true;
		
		private float _turningAngle;
        private float _raycastOffset;
        private CollisionDirection _preDirection;
		public CollisionDirection curDirection{get; private set;}
        
        private float _t;
		private MoveInfo _moveInfo;
        private LayerMask _triggerMask, _worldMask;
		
		private SnapAngle _snapAngle;
		private Vector3 _clockWiseScale, _counterClockWiseScale;
		private float _xScale, _yScale;
	    private Vector3[] stickToWallRotations;
		
		private enum JumpState{ Landed = 0, Jumping = 1}
		public Animator anim;
        void Awake ()
        {		
				//Debug.LogError("instantiate");
				_raycastOffset = 0.02f;//GetComponent<CharacterController>().radius / 4 * _xScale;
				
				moveDirection = Vector3.right;
                verticalDirection = Vector3.up;
				_moveInfo.isTurning = false;
				_t = 0.0f;
				_worldMask = 1<<9;
				_triggerMask = 1<<10;
				_snapAngle = new SnapAngle();
				_turningAngle = 90.0f / (turningTime / Time.fixedDeltaTime);

				_counterClockWiseScale = transform.localScale;
				_clockWiseScale = new Vector3(-_counterClockWiseScale.x, _counterClockWiseScale.y, _counterClockWiseScale.z);
				_xScale = Mathf.Abs(_clockWiseScale.x);
				_yScale = Mathf.Abs(_clockWiseScale.y);
				stickToWallRotations = new Vector3[(int)CollisionDirection.None];
				stickToWallRotations[(int)CollisionDirection.Left] = new Vector3(0, 0, 270);
				stickToWallRotations[(int)CollisionDirection.Right] = new Vector3(0, 0, 90);
				stickToWallRotations[(int)CollisionDirection.Up] = new Vector3(0, 0, 180);
				stickToWallRotations[(int)CollisionDirection.Down] = new Vector3(0, 0, 0);
				
				anim = GetComponent<Animator>();
				anim.SetBool("IsJumping", false);
        }
        
        void Start () {
				SetColliderSize(JumpState.Landed);
				CollisionDetection.Instance.rayDist = _raycastOffset;
                controller = GetComponent<CharacterController>();
                _preDirection = CollisionDetection.Instance.GetCollisionDirection(moveDirection, transform.position);
        }
        
        
        void FixedUpdate () {
                if(GlobalInfo.gameState == GameStates.Play && CameraMovment.Instance.cameraState == CameraState.Hover){
#if UNITY_STANDALONE || UNITY_WEBPLAYER || UNITY_EDITOR
					   clickToJump();
#endif
#if UNITY_IPONE || UNITY_ANDROID
				tabToJump();
#endif
		               curDirection = CollisionDetection.Instance.GetCollisionDirection(moveDirection, transform.position);
					//CollisionDetection.Instance.GetMidPntCollisionDirection(moveDirection, transform.position);
		              
						checkObstacleHit();
		                
		                checkColliderHit();
		                ApplyGravity ();
		            
		                ApplyJumping ();
		//	Debug.Log(curDirection);// + "," + moveDirection + "," + verticalDirection);
					//  Debug.Log(verticalSpeed + ":" + _curDirection);
		            /*
		             * if it is not turning at the outer corner
		             */ 
					if(!_moveInfo.isTurning){
						if(CollisionDetection.Instance.IsGoingToTurn(moveDirection, curDirection)){
							transform.position += GetDistanceToTurningPnt() * moveDirection;
							_moveInfo = CollisionDetection.Instance.GetTurningDirection(transform.position, moveDirection, _preDirection);
						}else{
							if(curDirection != CollisionDirection.None){
								if(isClockwise){
									transform.localScale = _clockWiseScale;
								}else{
									transform.localScale = _counterClockWiseScale;
								}	
								transform.rotation = Quaternion.Euler(stickToWallRotations[(int)curDirection]);
							}
							Vector3 movement = moveDirection * moveSpeed + verticalSpeed * verticalDirection;//new Vector3 (0, verticalSpeed, 0);
			                movement *= Time.deltaTime;
							if(curDirection != CollisionDirection.None){
								controller.Move(AdjustHeight());
							}
			                controller.Move(movement);
							
						}
					
						_toJump = false;
						
					}
					/*
					 * if it is turning...
					 */ 
					else{
						_t += Time.deltaTime;
						float delta = _t / turningTime;
						bool keepTurning = true;
						if(_toJump && verticalSpeed != 0.0f){
							Vector3 movement = moveDirection * moveSpeed + verticalSpeed * verticalDirection;
							movement *= Time.deltaTime;
							if(curDirection != CollisionDirection.None){
								controller.Move(AdjustHeight());
							}
							controller.Move(movement);
							keepTurning = false;
						}
						
						if(keepTurning){
							/*
								 * Still turning...
								 */
							if(delta <= 1){
								CollisionDetection.Instance.TurnAround(delta, _turningAngle, transform);
								
							}
							/*
								 * Finished turning...
								 */ 
							else{
								if(_moveInfo.isTurning){
									moveDirection = _moveInfo.moveDirection;
									verticalDirection = _moveInfo.VerticalDirection;
								}
								string tag = CollisionDetection.Instance.GetCollisionTag(transform.position);
								
								if(tag.Equals(GlobalInfo.KEY_TAG)){
						//	Debug.Log("here");
									CollisionDetection.Instance.GetCollisionObj(transform.position).transform.parent.GetComponent<KeyHole>().AddOneCornerCount();
								}else if(tag.Equals(GlobalInfo.EXIT_TAG)){
									GameManager.Instance.LoadNextScene();
								}
								transform.rotation = Quaternion.Euler( _snapAngle.GetSnappedAngle(transform.rotation.eulerAngles));
								_moveInfo.isTurning = false;
								_t = 0.0f;
							}
						}else{
							_moveInfo.isTurning = false;
						}
						
						
					}
				  _preDirection = curDirection;
	                
				}
                
        }
        
		void clickToJump(){
				if (Input.GetKeyDown(KeyCode.J)|| Input.GetKeyDown(KeyCode.Space))
				{
//					Debug.Log("jump!!!!");
					_toJump = true;
				//	lastJumpButtonTime = Time.time;
				}
		}

		void tabToJump(){
		if(Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began){
			Debug.Log("Tab!");
			_toJump = true;
		}
		}
        void checkColliderHit()
        {
               	if(_preDirection != curDirection)
				{
			 		 OnColliderHit();
				}           
              
        }
        
        void ApplyJumping ()
        {
                // Prevent jumping too fast after each other
                //if (lastJumpTime + jumpRepeatTime > Time.time)
                //        return;
                
                if (IsGrounded() && _toJump){
                        // Jump
                        // - Only when pressing the button down
                        // - With a timeout so you can press the button slightly before landing         
                      //  if (Time.time <= lastJumpButtonTime ){
					//	Debug.Log("jump!");
                        verticalSpeed = CalculateJumpVerticalSpeed (jumpHeight);
                        _hPosBeforeJump = transform.position.x;
                        _vPosBeforeJump = transform.position.y;
						anim.SetBool("IsJumping", true);
						string tag = CollisionDetection.Instance.GetCollisionTag(transform.position);
						if(tag.Equals(GlobalInfo.KEY_TAG)){
							CollisionDetection.Instance.GetCollisionObj(transform.position).transform.parent.GetComponent<KeyHole>().JumpOut();
						}

						//SetColliderSize(JumpState.Jumping);
                     //   }
				}
        }
        
        void ApplyGravity ()
        {
               
                if(_isGrounded){
                        if(curDirection == CollisionDirection.None)
                                _isGrounded = false;
                }
//                if (IsGrounded () && !_toJump){
//                     //   verticalSpeed = 0.0f;
//				}
//                else{
				if(!IsGrounded()){
                        verticalSpeed -= gravity * Time.deltaTime;
// 						if(verticalSpeed <= -CalculateJumpVerticalSpeed(jumpHeight)){
//							verticalSpeed = 0.0f;
//						}
				}else{
					verticalSpeed = 0.0f;
				}
        }
        
        float CalculateJumpVerticalSpeed (float targetJumpHeight)
        {
        // From the jump height and gravity we deduce the upwards speed 
        // for the character to reach at the apex.
                return Mathf.Sqrt(2 * targetJumpHeight * gravity);
        }
        
        void OnColliderHit()
        {
                // We are in jump mode but just became grounded
                if (IsGrounded())
                {     
                    justLanded();
					//SetColliderSize(JumpState.Landed);
                    if(!_isGrounded){
//                            Debug.Log("jump before grounded");
                            UpdateMovingDirectionAfterJump(curDirection);
                    }
					else{
//                            Debug.Log("didn't jump before grounded");
                            UpdateMovingDirectionWithoutJump(curDirection);
                    }
                }
                _isGrounded = true;
        }       
        
        void UpdateMovingDirectionAfterJump(CollisionDirection direction)
        {
                verticalSpeed = 0;
                Vector3 preDirection = moveDirection;
				Vector3 preVDirection = verticalDirection;
                //before jump, the cube is moving horizontally
                if(moveDirection == Vector3.left || moveDirection == Vector3.right){
                       //the forward direction
                        if(_vPosAfterLanded > _vPosBeforeJump){
                                moveDirection = Vector3.up;
                        }else{
                                moveDirection = Vector3.down;
                        }
                        //the jump direction
			
                        if(direction == CollisionDirection.Left){
                                verticalDirection = Vector3.right;
                        }else if(direction == CollisionDirection.Right){
                                verticalDirection = Vector3.left;
                        }else if(direction == CollisionDirection.Up){
                                moveDirection = preDirection;
                                verticalDirection = Vector3.down;
                        }else if(direction == CollisionDirection.Down){
                                moveDirection = preDirection;
                                verticalDirection = Vector3.up;
                        }
					
						if(preVDirection == - verticalDirection){
							isClockwise = !isClockwise;
						}else if((preDirection == Vector3.left && moveDirection == Vector3.up) 
							|| (preDirection == Vector3.right && moveDirection == Vector3.down)){
							isClockwise = true;
						}else if((preDirection == Vector3.right && moveDirection == Vector3.up) 
							|| (preDirection == Vector3.left && moveDirection == Vector3.down)){
							isClockwise = false;
						}
		                          
                }
                //before jump, the cube is moving vertically
                else{
                        
                        //the forward direction
                        if(_hPosAfterLanded > _hPosBeforeJump){
                                moveDirection = Vector3.right;
                        }else{
                                moveDirection = Vector3.left;
                        }
                        //the jump direction
                        if(direction == CollisionDirection.Up){
                                verticalDirection = Vector3.down;
                        }else if(direction == CollisionDirection.Down){
                                verticalDirection = Vector3.up;
                        }else if(direction == CollisionDirection.Left){
                                moveDirection = preDirection;
                                verticalDirection = Vector3.right;
                        }else if(direction == CollisionDirection.Right){
                                moveDirection = preDirection;
                                verticalDirection = Vector3.left;
                        }
						if(preVDirection == - verticalDirection){
							isClockwise = !isClockwise;
						}else if((preDirection == Vector3.up && moveDirection == Vector3.right) 
							|| (preDirection == Vector3.down && moveDirection == Vector3.left)){
							isClockwise = true;
						}else if((preDirection == Vector3.up && moveDirection == Vector3.left) 
							|| (preDirection == Vector3.down && moveDirection == Vector3.right)){
							isClockwise = false;
						}
                }
        }
        
        void UpdateMovingDirectionWithoutJump(CollisionDirection direction)
        {
			AdjustMovementBeforeChangingDirection();
			if(!isClockwise){
				switch(direction)
	                {
	                case CollisionDirection.Right: 
	                        moveDirection = Vector3.up;
	                        verticalDirection = Vector3.left;
	                        verticalSpeed = 0;
	                        break;
	                case CollisionDirection.Up: 
	                        moveDirection = Vector3.left;
	                        verticalDirection = Vector3.down;
	                        verticalSpeed = 0;
	                        break;
	                case  CollisionDirection.Left: 
	                        moveDirection = Vector3.down;
	                        verticalDirection = Vector3.right;
	                        verticalSpeed = 0;
	                        break;
	                case  CollisionDirection.Down: 
	                        moveDirection = Vector3.right;
	                        verticalDirection = Vector3.up;
	                        verticalSpeed = 0;
	                        break;
	                }
			}else{
				switch(direction)
	                {
	                case CollisionDirection.Right: 
	                        moveDirection = Vector3.down;
	                        verticalDirection = Vector3.left;
	                        verticalSpeed = 0;
	                        break;
	                case CollisionDirection.Up: 
	                        moveDirection = Vector3.right;
	                        verticalDirection = Vector3.down;
	                        verticalSpeed = 0;
	                        break;
	                case  CollisionDirection.Left: 
	                        moveDirection = Vector3.up;
	                        verticalDirection = Vector3.right;
	                        verticalSpeed = 0;
	                        break;
	                case  CollisionDirection.Down: 
	                        moveDirection = Vector3.left;
	                        verticalDirection = Vector3.up;
	                        verticalSpeed = 0;
	                        break;
	                }
			}
                
        }
        
        bool IsGrounded () 
        {
                return (curDirection != CollisionDirection.None);
        }
        
        void justLanded()
        {
                if(IsGrounded()){
                        _hPosAfterLanded = transform.position.x;
                        _vPosAfterLanded = transform.position.y;
						anim.SetBool("IsJumping", false);
                }
        }
        
        private void checkObstacleHit()
        {
                //die
				string tag = CollisionDetection.Instance.GetCollisionTag(transform.position);
				//Debug.Log(tag);
                if(tag == GlobalInfo.OBSTACLE_TAG){
						CheckPointMgr.Instance.Respawn();
						GameManager.Instance.UpdateDeathNum();
						CameraMovment.Instance.SetMainCtrl(null);
                        Destroy(gameObject);
                }
        }
	
	
	
		private Vector3 AdjustHeight(){
			RaycastHit hit;
			float adjDistance;
			switch(curDirection){
			case CollisionDirection.Left:
				if(Physics.Raycast(transform.position, Vector3.left, out hit, _xScale / 2 + _raycastOffset, _worldMask)){
					adjDistance = hit.distance - _xScale / 2;
					return adjDistance * Vector3.left;
				}else{
					RaycastHit hit1, hit2;
					if(Physics.Raycast(transform.position + _yScale / 2 * Vector3.up, Vector3.left, out hit1, _xScale / 2 + _raycastOffset, _worldMask)
					||Physics.Raycast(transform.position - _yScale / 2 * Vector3.up, Vector3.left, out hit2, _xScale / 2 + _raycastOffset, _worldMask)){
						adjDistance = Mathf.Min(hit1.distance - _xScale / 2, hit2.distance - _xScale / 2);
						return adjDistance * Vector3.left;
					}
				}
			break;
			case CollisionDirection.Right:
				if(Physics.Raycast(transform.position, Vector3.right, out hit, _xScale / 2 + _raycastOffset, _worldMask)){
					adjDistance = hit.distance  - _xScale / 2;
					return adjDistance * Vector3.right;
				}else{
					RaycastHit hit1, hit2;
					if(Physics.Raycast(transform.position + _yScale / 2 * Vector3.up, Vector3.right, out hit1, _xScale / 2 + _raycastOffset, _worldMask)
					||Physics.Raycast(transform.position - _yScale / 2 * Vector3.up, Vector3.right, out hit2, _xScale / 2 + _raycastOffset, _worldMask)){
						adjDistance = Mathf.Min(hit1.distance - _xScale / 2, hit2.distance - _xScale / 2);
						return adjDistance * Vector3.left;
					}
				}
			break;
			case CollisionDirection.Up:
				if(Physics.Raycast(transform.position, Vector3.up, out hit, _xScale / 2 + _raycastOffset, _worldMask)){
					adjDistance = hit.distance - _yScale / 2;
					return adjDistance * Vector3.up;
				}else{
					RaycastHit hit1, hit2;
					if(Physics.Raycast(transform.position + _xScale / 2 * Vector3.right, Vector3.up, out hit1, _xScale / 2 + _raycastOffset, _worldMask)
					||Physics.Raycast(transform.position - _xScale / 2 * Vector3.right, Vector3.up, out hit2, _xScale / 2 + _raycastOffset, _worldMask)){
						adjDistance = Mathf.Min(hit1.distance - _xScale / 2, hit2.distance - _xScale / 2);
						return adjDistance * Vector3.left;
					}
				}
			break;
			case CollisionDirection.Down:
				if(Physics.Raycast(transform.position, Vector3.down, out hit, _xScale / 2 + _raycastOffset, _worldMask)){
					adjDistance = hit.distance - _yScale / 2;
					return adjDistance * Vector3.down;
				}else{
					RaycastHit hit1, hit2;
					if(Physics.Raycast(transform.position + _xScale / 2 * Vector3.right, Vector3.down, out hit1, _xScale / 2 + _raycastOffset, _worldMask)
					||Physics.Raycast(transform.position - _xScale / 2 * Vector3.right, Vector3.down, out hit2, _xScale / 2 + _raycastOffset, _worldMask)){
						adjDistance = Mathf.Min(hit1.distance - _xScale / 2, hit2.distance - _xScale / 2);
						return adjDistance * Vector3.left;
					}
				}
			break;
			}	
			return Vector3.zero;
		}
		private float GetDistanceToTurningPnt(){
			RaycastHit hit;
			if(Physics.Raycast(transform.position, moveDirection, out hit, _xScale, _triggerMask)){
			//	Debug.DrawRay(transform.position, moveDirection, Color.red);
				return Vector3.Distance(transform.position, hit.point);
			}
			return 0.0f;
		}
		
		private void AdjustMovementBeforeChangingDirection(){
			RaycastHit hit;
			if(Physics.Raycast(transform.position, moveDirection, out hit, _xScale, _worldMask)){
				float adjDist = Vector3.Distance(transform.position, hit.point);
				transform.position += (adjDist - _xScale / 2) * moveDirection;
			}
		}

	private void SetColliderSize(JumpState js){
		CollisionDetection.Instance.xDist = js == JumpState.Jumping ? _xScale / 1.414f : _xScale;
		CollisionDetection.Instance.yDist = js == JumpState.Jumping ?  _yScale / 1.414f : _yScale;
	}
	
		
		

}